package br.usp.trabalho.sd.master;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import br.usp.trabalho.sd.InputAction;
import br.usp.trabalho.sd.StatusCode;
import br.usp.trabalho.sd.FileInfo;


public class MasterServer implements Runnable
{
    private static final int PORT = 60050;

    private ServerSocket     mServerSocket;
    private NodeListManager  mNodeList;
    private FilesManager     mFilesManager;


    public MasterServer() throws IOException
    {
        mServerSocket = new ServerSocket(PORT);
        mNodeList = new NodeListManager();
    }

    public void start()
    {
        new Thread(this).start();
    }

    @Override
    public void run()
    {
        boolean running = true;
        while (running)
        {
            try
            {
                Socket socket = mServerSocket.accept();
                new NodeConnection(socket).start();
            }
            catch (IOException e)
            {
                e.printStackTrace();
                running = false;
            }
        }
    }

    @Override
    protected void finalize() throws Throwable
    {
        mNodeList.saveNodeList();
        mServerSocket.close();
        super.finalize();
    }
    
    class NodeConnection extends Thread
    {
        private String             mHostName;

        private Socket             mSocket;
        private ObjectOutputStream mOutputStream;
        private ObjectInputStream  mInputStream;


        public NodeConnection(Socket socket) throws IOException
        {
            mSocket = socket;
            mOutputStream = new ObjectOutputStream(mSocket.getOutputStream());
            mInputStream = new ObjectInputStream(mSocket.getInputStream());

            mHostName = mServerSocket.getInetAddress().getHostName();
            String ip = mServerSocket.getInetAddress().getHostAddress();

            if (mNodeList.addNewNodeToList(mHostName, ip))
            {
                sendMessage("Conectado ao servidor central");
                mOutputStream.flush();
            }
        }

        @Override
        public void run()
        {
            boolean running = true;
            while (running)
            {
                try
                {
                    switch ((InputAction) mInputStream.readObject())
                    {
                    case ADD:
                    {
                        // Request the file name
                        sendMessage(StatusCode.OK);
                        mOutputStream.flush();

                        // Read the file info
                        FileInfo info = (FileInfo) mInputStream.readObject();

                        // Try to save the file
                        StatusCode statusCode = StatusCode.CANCEL;
                        if (mFilesManager.saveFile(info))
                        {
                            sendMessage(StatusCode.OK);
                        }
                        sendMessage(statusCode);
                        mOutputStream.flush();
                        break;
                    }

                    case REMOVE:
                    case UPDATE:
                    case DOWNLOAD:
                    {
                        // Request the file name
                        sendMessage(StatusCode.WAITING_FOR_FILENAME);
                        mOutputStream.flush();

                        // Read the file name
                        String fileName = (String) mInputStream.readObject();

                        // Try to load its info
                        FileInfo info = mFilesManager.loadFile(fileName);
                        if (info != null)
                        {
                            // Send the node info
                            sendMessage(StatusCode.OK);
                            String host = info.getFileHost();
                            sendMessage(mNodeList.getNodeInfo(host));
                            mOutputStream.flush();

                            connectClientToNode(host);
                        }
                        else
                        {
                            sendMessage(StatusCode.FILE_NOT_FOUND);
                            mOutputStream.flush();
                        }
                        break;
                    }
    
                    case LEAVE:
                        sendMessage(StatusCode.CANCEL);
                        running = false;
                        break;
    
                    default:
                        break;
                    }
                }
                catch (ClassNotFoundException | IOException e)
                {
                    e.printStackTrace();
                }
            }
        }

        private void connectClientToNode(String host)
        {
            try
            {
                if ((StatusCode) mInputStream.readObject() == StatusCode.CONNECTED_TO_NODE)
                {
                    // Sets the node as busy
                    mNodeList.setNodeAsBusy(host);
                    sendMessage(StatusCode.OK);
                    mOutputStream.flush();
    
                    while (true)
                    {
                        switch ((StatusCode) mInputStream.readObject())
                        {
                        case DISCONNECTED_FROM_NODE:
                            // Free the node resources
                            mNodeList.freeNode(host);
                            sendMessage(StatusCode.OK);
                            mOutputStream.flush();
                            break;

                        default:
                            break;
                        }
                    }
                }
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            catch (ClassNotFoundException e)
            {
                e.printStackTrace();
            }
        }

        public void sendMessage(Object message)
        {
            try
            {
                mOutputStream.writeObject(message);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }

        @Override
        protected void finalize() throws Throwable
        {
            // Remove the node from the node list
            if (mNodeList.removeNodeFromList(mHostName))
            {
                sendMessage("Desconectado do servidor central");
            }

            mOutputStream.close();
            mInputStream.close();
            mSocket.close();
            super.finalize();
        }
    }
}
